[備忘録] 同期関数/非同期関数で期待通りの例外がスローされたことをJestで評価する
こんにちは、CX事業本部 Delivery部の若槻です。
今回は、同期関数/非同期関数で期待通りの例外がスローされたことをJestで評価することがよくあるのですが、その際のテストの記述をよく忘れてはググっているので、備忘としてまとめておきます。
先にまとめ
同期関数/非同期関数の例外のスローは、それぞれ次の記述でテストできます。
test('_syncFunc', () => { //同期関数のテスト expect(() => syncFunc()).toThrow(new Error('syncFunc failed.')); }); test('_asyncFunc', async () => { //非同期関数のテスト await expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.')); });
解説
Jestで呼び出された関数が例外をスローしたことを評価したい場合は、.toThrow(error?)
を使用します。
Use
.toThrow
to test that a function throws when it is called.
また、JavaScriptの非同期関数は実行結果としてPromiseを返します。
Promiseがreject(例外をスロー)することを期待したテストを実施したい場合、Jestではrejects
マッチャーを使用します。
If you expect a promise to be rejected, use the .rejects matcher. It works analogically to the .resolves matcher. If the promise is fulfilled, the test will automatically fail.
検証
次の関数で例外がスローされていることをテストしたいとします。
export const syncFunc = () => { subFunc(); throw new Error('syncFunc failed.'); }; export const asyncFunc = async () => { subFunc(); throw new Error('asyncFunc failed.'); }; export const subFunc = () => { console.log('hoge'); };
正しいパターン
まず、冒頭で示した、正しいパターンの記述によりテストが正常にPASSするパターンです。
import { syncFunc, asyncFunc, subFunc } from '../func'; jest.setTimeout(10000); test('_syncFunc', () => { (subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0); //同期関数のテスト - 正しいパターン expect(() => syncFunc()).toThrow(new Error('syncFunc failed.')); expect(subFunc).toBeCalledTimes(1); }); test('_asyncFunc', async () => { (subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0); //非同期関数のテスト - 正しいパターン await expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.')); expect(subFunc).toBeCalledTimes(1); });
いずれのテストもPASSします。
$ npx jest PASS test/func.test.ts (5.387 s) ✓ _syncFunc (5 ms) ✓ _asyncFunc (3002 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 5.444 s Ran all test suites.
正しくないパターン
次に、よくやりがちな、正しくないパターンの記述によりテストがFAILするパターンです。
import { syncFunc, asyncFunc, subFunc } from '../func'; jest.setTimeout(10000); test('_syncFunc', () => { (subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0); //同期関数のテスト - 正くないパターン(無名関数となっていない) expect(syncFunc()).toThrow(new Error('syncFunc failed.')); expect(subFunc).toBeCalledTimes(1); }); test('_asyncFunc', async () => { (subFunc as jest.Mock) = jest.fn().mockReturnValue(void 0); //非同期関数のテスト - 正しくないパターン(expectが同期呼び出しされていない) expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.')); expect(subFunc).toBeCalledTimes(1); });
同期関数のテストを実行すると失敗しました。syncFunc
がスローした例外がマッチャー側でキャッチできず関数の実行自体がFAILしています。
$ npx jest -t "_syncFunc" FAIL test/func.test.ts ✕ _syncFunc (1 ms) ○ skipped _asyncFunc ● _syncFunc syncFunc failed. 1 | export const syncFunc = () => { 2 | subFunc(); > 3 | throw new Error('syncFunc failed.'); | ^ 4 | }; 5 | 6 | export const asyncFunc = async () => { at syncFunc (func.ts:3:9) at Object.<anonymous> (test/func.test.ts:9:18) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 skipped, 2 total Snapshots: 0 total Time: 2.248 s, estimated 3 s Ran all test suites with tests matching "_syncFunc".
非同期関数のテストの実行も失敗しました。asyncFunc
の同期呼び出しが完了する前にsubFunc
の呼び出しが評価されており、テストがFAILしています。また非同期呼び出しされた関数の実行が完了する前にJestのテスト実行が終了しているため、Jest did not exit one second after the test run has completed.
というWarningが出ています。
$ npx jest -t "_asyncFunc" FAIL test/func.test.ts ✕ _asyncFunc (2 ms) ○ skipped _syncFunc ● _asyncFunc expect(jest.fn()).toBeCalledTimes(expected) Expected number of calls: 1 Received number of calls: 0 18 | expect(asyncFunc()).rejects.toThrow(new Error('asyncFunc failed.')); 19 | > 20 | expect(subFunc).toBeCalledTimes(1); | ^ 21 | }); 22 | at Object.<anonymous> (test/func.test.ts:20:19) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 skipped, 2 total Snapshots: 0 total Time: 2.182 s, estimated 3 s Ran all test suites with tests matching "_asyncFunc". Jest did not exit one second after the test run has completed. This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
参考
以上